package com.proxy.scanandcheck.scaner.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;

import com.proxy.scanandcheck.checker.controller.ProxyCheckingController;
import com.proxy.scanandcheck.common.util.Constants;
import com.proxy.scanandcheck.common.util.IPUtil;
import com.proxy.scanandcheck.common.util.RandomUtil;
import com.proxy.scanandcheck.common.valueobject.Proxy;
import com.proxy.scanandcheck.common.valueobject.ProxyRequest;
import com.proxy.scanandcheck.scaner.dao.ScannerDao;
import com.proxy.scanandcheck.scaner.valueobject.IpAddress;
import com.proxy.scanandcheck.scaner.valueobject.StartEndIp;

public abstract class AbstractScannerThreadPool {
	
	private static final Logger LOGGER = Logger.getLogger(AbstractScannerThreadPool.class);
	
	private ThreadPoolExecutor threadPoolExecutor;
	
	private ScannerDao scannerDao;
	
	private ProxyCheckingController proxyCheckingController;
	
	private boolean started = Boolean.FALSE;
	
	public boolean executeForFullRange(ProxyRequest proxyRequest) throws InterruptedException, ExecutionException
	{
		started = Boolean.TRUE;
		
		threadPoolExecutor = new ThreadPoolExecutor(proxyRequest.getThreadCount(), 
				Integer.MAX_VALUE, Constants.KEEP_ALIVE_TIME,
				TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000),
				new ThreadPoolExecutor.CallerRunsPolicy());
		
		long startIpLong = 0,endIpLong = 0;
		
		int rangeSize = RandomUtil.getRandomIntNum(
							Constants.IP_RANGE_SIZE_MIN, Constants.IP_RANGE_SIZE_MAX);
		
		if(proxyRequest.getIpClass().equals(ProxyRequest.A_CLASS))
		{
			startIpLong = Constants.AClassStartIpLong;
			endIpLong = Constants.AClassEndIpLong;
		}
		else if(proxyRequest.getIpClass().equals(ProxyRequest.B_CLASS))
		{
			startIpLong = Constants.BClassStartIpLong;
			endIpLong = Constants.BClassEndIpLong;
		}
		else if(proxyRequest.getIpClass().equals(ProxyRequest.C_CLASS))
		{
			startIpLong = Constants.CClassStartIpLong;
			endIpLong = Constants.CClassEndIpLong;
		}
		
		int i = 0;
		List<ScannerThreadPoolTask> scannerThreadPoolTaskList = new ArrayList<ScannerThreadPoolTask>();
		List<IpAddress> ipAddressList = scannerDao.getRandomIpAddressList(proxyRequest);
		for(long ipLong = startIpLong; ipLong < endIpLong; ipLong ++)
		{
			String ip = IPUtil.long2IP(ipLong);
			if(!IPUtil.isPrivateIp(ip))
			{
				for(int port = 0; port < Constants.PORTS.length; port ++)
				{
					IpAddress ipAddress = new IpAddress(ip, Constants.PORTS[port], proxyRequest.getIpClass());
					ipAddressList.add(ipAddress);
					ScannerThreadPoolTask scannerThreadPoolTask = new ScannerThreadPoolTask(ipAddress);
					scannerThreadPoolTaskList.add(scannerThreadPoolTask);
//					LOGGER.info(ipAddress);
					
					i ++;
					if(i % 1000 == 0)
					{
						LOGGER.info("------ Scan from " + 
								ipAddressList.get(0) + 
								" - " + 
								ipAddressList.get(ipAddressList.size() - 1) + 
								", size: " + 
								ipAddressList.size());
						
						threadPoolExecutor.invokeAll(scannerThreadPoolTaskList);
						
						proxyCheckingController.startCheck(
								getProxyList(ipAddressList), 
								proxyRequest.getKeyWordsList());
						
						ipAddressList.clear();
						scannerThreadPoolTaskList.clear();
						
						Thread.sleep(2000);
					}
				}
			}
		}
		return Boolean.TRUE;
	}
	
	public boolean execute(ProxyRequest proxyRequest) throws InterruptedException, ExecutionException
	{
		started = Boolean.TRUE;
		
		threadPoolExecutor = new ThreadPoolExecutor(proxyRequest.getThreadCount(), 
				Integer.MAX_VALUE, Constants.KEEP_ALIVE_TIME,
				TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000),
				new ThreadPoolExecutor.CallerRunsPolicy());
		
		boolean breakMe = Boolean.FALSE;
		while(isStarted())
		{
			List<IpAddress> ipAddressList = this.getIpAddressList(proxyRequest);
			if(ipAddressList == null || ipAddressList.isEmpty())
			{
				break;
			}
			List<ScannerThreadPoolTask> scannerThreadPoolTaskList = new ArrayList<ScannerThreadPoolTask>();
			for(IpAddress ipAddress: ipAddressList)
			{
				ScannerThreadPoolTask scannerThreadPoolTask = new ScannerThreadPoolTask(ipAddress);
				scannerThreadPoolTaskList.add(scannerThreadPoolTask);
			}
			long listSize = IPUtil.ip2Long(ipAddressList.get(ipAddressList.size() - 1).getAddress()) - 
					IPUtil.ip2Long(ipAddressList.get(0).getAddress());
			
			if(scannerThreadPoolTaskList.size() > 1000)
			{
				int fromIndex = 0,toIndex = 0,i = 0;
				int length = 1000;
				int scannerThreadPoolTaskListsize = scannerThreadPoolTaskList.size();
				while(isStarted())
				{
					fromIndex = length * i;
					toIndex = fromIndex + length - 1;
					if(fromIndex >= scannerThreadPoolTaskListsize)
						break;
					
					if(toIndex > scannerThreadPoolTaskListsize)
						toIndex = scannerThreadPoolTaskListsize - 1;
					
					
					List<ScannerThreadPoolTask> tempscannerThreadPoolTaskList = 
							scannerThreadPoolTaskList.subList(fromIndex, toIndex);
					threadPoolExecutor.invokeAll(tempscannerThreadPoolTaskList);
					
					LOGGER.info("------ Scan from " + 
							tempscannerThreadPoolTaskList.get(0).getIpAddress() + 
							" - " + 
							tempscannerThreadPoolTaskList.get(tempscannerThreadPoolTaskList.size() - 1).getIpAddress() + 
							", size: " + 
							tempscannerThreadPoolTaskList.size());
					
					proxyCheckingController.startCheck(
							getProxyList(ipAddressList.subList(fromIndex, toIndex)), 
							proxyRequest.getKeyWordsList());
					
					if(toIndex == scannerThreadPoolTaskListsize - 1)
					{
						breakMe = Boolean.TRUE;
						break;
					}
					i++;
				}
			}
			else
			{
				LOGGER.info("------ Scan from " + 
						ipAddressList.get(0) + 
						" - " + 
						ipAddressList.get(ipAddressList.size() - 1) + 
						", size: " + 
						ipAddressList.size());
				threadPoolExecutor.invokeAll(scannerThreadPoolTaskList);
				
				proxyCheckingController.startCheck(
						getProxyList(ipAddressList), 
						proxyRequest.getKeyWordsList());
			}
			
			
			Thread.sleep(3000);
		}
		
		return Boolean.TRUE;
	}
	
	protected List<Proxy> getProxyList(List<IpAddress> ipAddressList)
	{
		List<Proxy> myProxyList = new ArrayList<Proxy>();
		for(IpAddress ipAddress : ipAddressList)
		{
			if(ipAddress.isValid())
			{
				Proxy proxy = new Proxy();
				proxy.setProxy(ipAddress.getAddress() + ":" + ipAddress.getPort());
				proxy.setIpClass(ipAddress.getIpClass());
				proxy.setUpdateTimeForGoogleHost(ipAddress.getUpdateTime());
				myProxyList.add(proxy);
				LOGGER.info("------ valid ip after scan: " + ipAddress);
			}
		}
		if(myProxyList.isEmpty())
		{
			String msg = "------ can't find valid ip after scan: " +
					ipAddressList.get(0) +
					" -- " + 
					ipAddressList.get(ipAddressList.size() - 1);
			LOGGER.info(msg);
		}
		return myProxyList;
	}
	
	public void shutdowThread()
	{
		started = Boolean.FALSE;
		// dont forget to shutdown executor
		if(threadPoolExecutor != null)
		{
			threadPoolExecutor.shutdown();
			threadPoolExecutor = null;
			/*try {
				// Wait for everything to finish.
				while (!threadPool.awaitTermination(2, TimeUnit.SECONDS)) {
//				System.out.println("Awaiting completion of threads.");
					Thread.sleep(100);
				}
				System.out.println("finished scanning port");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}*/
		}
	}
	
	public abstract List<IpAddress> getIpAddressList(ProxyRequest proxyRequest);

	public synchronized boolean isStarted() {
		return started;
	}

	public void setStarted(boolean started) {
		this.started = started;
	}

	public ThreadPoolExecutor getThreadPoolExecutor() {
		return threadPoolExecutor;
	}

	public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
		this.threadPoolExecutor = threadPoolExecutor;
	}

	public ScannerDao getScannerDao() {
		return scannerDao;
	}

	public void setScannerDao(ScannerDao scannerDao) {
		this.scannerDao = scannerDao;
	}

	public ProxyCheckingController getProxyCheckingController() {
		return proxyCheckingController;
	}

	public void setProxyCheckingController(
			ProxyCheckingController proxyCheckingController) {
		this.proxyCheckingController = proxyCheckingController;
	}

}
